﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework;

namespace Project
{
    public class Scene
    {
        protected GraphicsDevice graphics;
        protected QuadRender quadRender;

        public Color ambientLight;
        public List<DirectionalLight> dirLights { get; protected set; }
        public List<PointLight> pointLights { get; protected set; }
        public List<SpotLight> spotLights { get; protected set; }

        public List<Renderable> renderables { get; protected set; }

        // 4 primary buffers with data directly from the first pass of rasterization
        private RenderTarget2D colorRT;
        private RenderTarget2D normalRT;
        private RenderTarget2D depthRT;
        private RenderTarget2D posRT; // view-space position

        // extra color buffer for intermediate steps
        private RenderTarget2D miscRT1;
        private RenderTarget2D miscRT2;
        private RenderTarget2D miscRT3;

        private Effect lightingEffect;

        public Scene(GraphicsDevice graphics, ContentManager content)
        {
            this.graphics = graphics;
            this.quadRender = new QuadRender(graphics);

            this.dirLights = new List<DirectionalLight>();
            this.pointLights = new List<PointLight>();
            this.spotLights = new List<SpotLight>();

            this.renderables = new List<Renderable>();
            initRenderTargets();
            lightingEffect = content.Load<Effect>("effects/lighting");

            ambientLight = new Color(0.12f, 0.12f, 0.12f);
            this.dirLights.Add(new DirectionalLight(new Vector3(-1f, -1f, -1.25f), new Color(0.5f, 0.6f, 0.5f)));
            this.dirLights.Add(new DirectionalLight(new Vector3(1f, 1f, 1.25f), new Color(0.5f, 0.5f, 0.6f)));
        }

        private void initRenderTargets()
        {
            int width = graphics.PresentationParameters.BackBufferWidth;
            int height = graphics.PresentationParameters.BackBufferHeight;
            colorRT = new RenderTarget2D(graphics, width, height, false, SurfaceFormat.Color, DepthFormat.Depth24, 0, RenderTargetUsage.DiscardContents);
            normalRT = new RenderTarget2D(graphics, width, height, false, SurfaceFormat.Rgba1010102, DepthFormat.Depth24, 0, RenderTargetUsage.DiscardContents);
            depthRT = new RenderTarget2D(graphics, width, height, false, SurfaceFormat.Single, DepthFormat.Depth24, 0, RenderTargetUsage.DiscardContents);

            miscRT1 = new RenderTarget2D(graphics, width, height, false, SurfaceFormat.Color, DepthFormat.Depth24, 0, RenderTargetUsage.PreserveContents);
            miscRT2 = new RenderTarget2D(graphics, width, height, false, SurfaceFormat.Color, DepthFormat.Depth24, 0, RenderTargetUsage.PreserveContents);
            miscRT3 = new RenderTarget2D(graphics, width, height, false, SurfaceFormat.Color, DepthFormat.Depth24, 0, RenderTargetUsage.PreserveContents);
        }

        public void render(GameTime gt, Camera camera, Vector3 eyePosition)
        {
            // render to deffered-shading buffers (color, normal, depth)
            graphics.SetRenderTargets(colorRT, normalRT, depthRT);
            graphics.BlendState = BlendState.Opaque;
            foreach (Renderable r in renderables)
            {
                r.render(gt, camera);
            }

            // render all light color to miscRT1
            graphics.SetRenderTarget(miscRT1);

            // set shader settings which won't change
            // inverse projection matrix (used to get world coordinates from position and depth)
            Matrix invWVP = Matrix.Invert(camera.view * camera.projection);
            lightingEffect.Parameters["invWVP"].SetValue(invWVP);
            lightingEffect.Parameters["eyePosition"].SetValue(eyePosition);
            lightingEffect.Parameters["fbDim"].SetValue(new Vector2(colorRT.Bounds.Width, colorRT.Bounds.Height));

            lightingEffect.Parameters["normal"].SetValue(normalRT);
            lightingEffect.Parameters["depth"].SetValue(depthRT);

            // ambient
            graphics.BlendState = BlendState.Opaque;
            lightingEffect.CurrentTechnique = lightingEffect.Techniques["Ambient"];
            lightingEffect.Parameters["color"].SetValue(colorRT);
            lightingEffect.Parameters["lightColor"].SetValue(ambientLight.ToVector4());
            quadRender.RenderFullScreenQuad(lightingEffect);

            graphics.BlendState = BlendState.Additive;
            // directional
            lightingEffect.CurrentTechnique = lightingEffect.Techniques["Directional"];
            foreach (DirectionalLight light in dirLights)
            {
                lightingEffect.Parameters["lightColor"].SetValue(light.color.ToVector4());
                lightingEffect.Parameters["lightDirection"].SetValue(light.direction);
                quadRender.RenderFullScreenQuad(lightingEffect);
            }
            // point
            lightingEffect.CurrentTechnique = lightingEffect.Techniques["Point"];
            foreach (PointLight light in pointLights)
            {
                lightingEffect.Parameters["lightC"].SetValue(light.attenC);
                lightingEffect.Parameters["lightL"].SetValue(light.attenL);
                lightingEffect.Parameters["lightQ"].SetValue(light.attenQ);
                lightingEffect.Parameters["lightColor"].SetValue(light.color.ToVector4());
                lightingEffect.Parameters["lightPosition"].SetValue(light.position);
                quadRender.RenderFullScreenQuad(lightingEffect);
            }
            // spot
            lightingEffect.CurrentTechnique = lightingEffect.Techniques["Spot"];
            foreach (SpotLight light in spotLights)
            {
                lightingEffect.Parameters["lightDirection"].SetValue(light.direction);
                lightingEffect.Parameters["cosCutOff"].SetValue(light.cosCutOff);
                lightingEffect.Parameters["lightC"].SetValue(light.attenC);
                lightingEffect.Parameters["lightL"].SetValue(light.attenL);
                lightingEffect.Parameters["lightQ"].SetValue(light.attenQ);
                lightingEffect.Parameters["lightColor"].SetValue(light.color.ToVector4());
                lightingEffect.Parameters["lightPosition"].SetValue(light.position);
                quadRender.RenderFullScreenQuad(lightingEffect);
            }

            // fog
            graphics.SetRenderTarget(miscRT3);
            graphics.BlendState = BlendState.Opaque;
            lightingEffect.CurrentTechnique = lightingEffect.Techniques["Fog"];
            lightingEffect.Parameters["color"].SetValue(miscRT1);
            lightingEffect.Parameters["fogC"].SetValue(1.0f);
            lightingEffect.Parameters["fogL"].SetValue(0.15f);
            lightingEffect.Parameters["fogQ"].SetValue(0.5f);
            lightingEffect.Parameters["lightColor"].SetValue(new Vector4(0, 0, 0, 1));
            quadRender.RenderFullScreenQuad(lightingEffect);

            // bloom:
            // pick out bright pixels
            graphics.SetRenderTarget(miscRT2);
            graphics.BlendState = BlendState.Opaque;
            lightingEffect.CurrentTechnique = lightingEffect.Techniques["Bright"];
            lightingEffect.Parameters["color"].SetValue(miscRT1);
            quadRender.RenderFullScreenQuad(lightingEffect);
            // blur
            graphics.SetRenderTarget(miscRT1);
            graphics.BlendState = BlendState.Opaque;
            lightingEffect.CurrentTechnique = lightingEffect.Techniques["BlurH"];
            lightingEffect.Parameters["color"].SetValue(miscRT2);
            quadRender.RenderFullScreenQuad(lightingEffect);
            // blur
            graphics.SetRenderTarget(miscRT2);
            graphics.BlendState = BlendState.Opaque;
            lightingEffect.CurrentTechnique = lightingEffect.Techniques["BlurV"];
            lightingEffect.Parameters["color"].SetValue(miscRT1);
            quadRender.RenderFullScreenQuad(lightingEffect);

            // overlay bloom on top
            graphics.BlendState = BlendState.AlphaBlend;
            graphics.SetRenderTarget(miscRT3);
            lightingEffect.CurrentTechnique = lightingEffect.Techniques["Ambient"];
            lightingEffect.Parameters["color"].SetValue(miscRT2);
            lightingEffect.Parameters["lightColor"].SetValue(new Vector4(1.00f, 1.00f, 1.00f, 0.05f));
            quadRender.RenderFullScreenQuad(lightingEffect);

            // Cartoon-style edge detection (via discrete laplace convolution)
            graphics.SetRenderTarget(miscRT2);
            graphics.BlendState = BlendState.Opaque;
            lightingEffect.CurrentTechnique = lightingEffect.Techniques["Laplace"];
            lightingEffect.Parameters["lightColor"].SetValue(new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
            quadRender.RenderFullScreenQuad(lightingEffect);

            // render de-noised edges on top
            graphics.SetRenderTarget(miscRT3);
            graphics.BlendState = BlendState.AlphaBlend;
            lightingEffect.CurrentTechnique = lightingEffect.Techniques["Denoise"];
            lightingEffect.Parameters["color"].SetValue(miscRT2);
            quadRender.RenderFullScreenQuad(lightingEffect);

            // render lit scene after bloom
            graphics.SetRenderTarget(null);
            graphics.BlendState = BlendState.Opaque;
            lightingEffect.CurrentTechnique = lightingEffect.Techniques["Basic"];
            lightingEffect.Parameters["color"].SetValue(miscRT3);
            quadRender.RenderFullScreenQuad(lightingEffect);
        }
    }
    public interface Renderable
    {
        /// <summary>
        /// Render all geometry using whatever shaders are appropriate for the model.
        /// Note that the effects used MUST output to multiple (3) render targets 
        /// according to the structure expected by Scene.
        /// </summary>
        void render(GameTime gt, Camera camera);
    }
    /// <summary>
    /// 
    /// </summary>
    public class QuadRender
    {
        private VertexPositionTexture[] verts;
        private GraphicsDevice myDevice;
        private short[] ib = null;

        ///
        /// Loads the quad.
        ///
        ///
        public QuadRender(GraphicsDevice device)
        {

            myDevice = device;

            verts = new VertexPositionTexture[]
                        {
                            new VertexPositionTexture(
                                new Vector3(0,0,0),
                                new Vector2(1,1)),
                            new VertexPositionTexture(
                                new Vector3(0,0,0),
                                new Vector2(0,1)),
                            new VertexPositionTexture(
                                new Vector3(0,0,0),
                                new Vector2(0,0)),
                            new VertexPositionTexture(
                                new Vector3(0,0,0),
                                new Vector2(1,0))
                        };

            ib = new short[] { 0, 1, 2, 2, 3, 0 };

        }

        ///
        /// Draws the fullscreen quad.
        ///
        ///
        public void RenderFullScreenQuad(Effect effect)
        {
            effect.CurrentTechnique.Passes[0].Apply();
            RenderQuad(Vector2.One * -1, Vector2.One);
        }

        public void RenderQuad(Vector2 v1, Vector2 v2)
        {

            verts[0].Position.X = v2.X;
            verts[0].Position.Y = v1.Y;

            verts[1].Position.X = v1.X;
            verts[1].Position.Y = v1.Y;

            verts[2].Position.X = v1.X;
            verts[2].Position.Y = v2.Y;

            verts[3].Position.X = v2.X;
            verts[3].Position.Y = v2.Y;

            myDevice.DrawUserIndexedPrimitives
                (PrimitiveType.TriangleList, verts, 0, 4, ib, 0, 2);
        }
    }
    public class DirectionalLight
    {
        public Vector3 direction;
        public Color color;
        public DirectionalLight(Vector3 direction, Color color)
        {
            this.direction = direction;
            this.color = color;
        }
    }

    public class PointLight
    {
        public Vector3 position;
        public Color color;
        public float attenC;
        public float attenL;
        public float attenQ;
        public PointLight(Vector3 position, Color color, float attenC, float attenL, float attenQ)
        {
            this.position = position;
            this.color = color;
            this.attenC = attenC;
            this.attenL = attenL;
            this.attenQ = attenQ;
        }
    }

    public class SpotLight
    {
        public Vector3 position;
        public Vector3 direction;
        public Color color;
        public float cosCutOff;
        public float attenC;
        public float attenL;
        public float attenQ;
        public SpotLight(Vector3 position, Vector3 direction, Color color, float cosCutOff, float attenC, float attenL, float attenQ)
        {
            this.position = position;
            this.direction = direction;
            this.color = color;
            this.cosCutOff = cosCutOff;
            this.attenC = attenC;
            this.attenL = attenL;
            this.attenQ = attenQ;
        }
    }
}
